import { $set } from '@feng3d/serialization';
import { Cache, Color, Fog3D, Mesh3D, MeshBasicMaterial3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, PlaneGeometry3D, Scene3D, Vector3, WebGLRenderer3D } from 'feng3d-three';
import GUI from 'lil-gui';
import { TextGeometry3D } from '../../extension/geometries/TextGeometry3D';
import { FontLoader3D } from '../../extension/loaders/FontLoader3D';
import { Font } from '../../extension/thirdparty/three/imports';

const rootPath = '../../';

Cache.enabled = true;

let container: HTMLDivElement;

let camera: PerspectiveCamera3D;
let cameraTarget: Vector3;
let scene: Scene3D;
let renderer: WebGLRenderer3D;

let group: Node3D;
let textMesh1: Mesh3D;
let textMesh2: Mesh3D;
let textGeo: TextGeometry3D;
let materials: MeshPhongMaterial3D[];

let firstLetter = true;

let text = 'three.js';

let bevelEnabled = true;

let font: Font;

let fontName = 'optimer'; // helvetiker, optimer, gentilis, droid sans, droid serif
let fontWeight = 'bold'; // normal bold

const height = 20;
const size = 70;
const hover = 30;

const curveSegments = 4;

const bevelThickness = 2;
const bevelSize = 1.5;

const mirror = true;

const fontMap = {

    helvetiker: 0,
    optimer: 1,
    gentilis: 2,
    'droid/droid_sans': 3,
    'droid/droid_serif': 4

};

const weightMap = {

    regular: 0,
    bold: 1

};

const reverseFontMap = [];
const reverseWeightMap = [];

for (const i in fontMap) reverseFontMap[fontMap[i]] = i;
for (const i in weightMap) reverseWeightMap[weightMap[i]] = i;

let targetRotation = 0;
let targetRotationOnPointerDown = 0;

let pointerX = 0;
let pointerXOnPointerDown = 0;

let windowHalfX = window.innerWidth / 2;

let fontIndex = 1;

init();
animate();

function init()
{
    container = document.createElement('div');
    document.body.appendChild(container);

    // CAMERA

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 30, aspect: window.innerWidth / window.innerHeight, near: 1, far: 1500 });
    camera.entity.position.set(0, 400, 700);

    cameraTarget = new Vector3(0, 150, 0);

    // SCENE

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0x000000);
    scene.fog = new Fog3D(0x000000, 250, 1400);

    // LIGHTS

    const dirLight = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffffff }, intensity: 0.125 });
    dirLight.entity.position.set(0, 0, 1).normalize();
    scene.entity.addChild(dirLight.entity);

    const pointLight = new Node3D().addComponent('PointLight3D', { color: { value: 0xffffff }, intensity: 1.5 });
    pointLight.color.setHSL(Math.random(), 1, 0.5);
    pointLight.entity.position.set(0, 100, 90);
    scene.entity.addChild(pointLight.entity);

    materials = [
        $set(new MeshPhongMaterial3D(), { color: { value: 0xffffff }, flatShading: true }), // front
        $set(new MeshPhongMaterial3D(), { color: { value: 0xffffff } }) // side
    ];

    group = new Node3D();
    group.position.y = 100;

    scene.entity.addChild(group);

    loadFont();

    const plane = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new PlaneGeometry3D(), { width: 10000, height: 10000 }),
        material: new MeshBasicMaterial3D({ color: 0xffffff, opacity: 0.5, transparent: true })
    });
    plane.entity.position.y = 100;
    plane.entity.rotation.x = -Math.PI / 2;
    scene.entity.addChild(plane.entity);

    // RENDERER

    renderer = new Node3D().addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    // EVENTS

    container.style.touchAction = 'none';
    container.addEventListener('pointerdown', onPointerDown);

    document.addEventListener('keypress', onDocumentKeyPress);
    document.addEventListener('keydown', onDocumentKeyDown);

    //

    const params = {
        changeColor()
        {
            pointLight.color.setHSL(Math.random(), 1, 0.5);
        },
        changeFont()
        {
            fontIndex++;

            fontName = reverseFontMap[fontIndex % reverseFontMap.length];

            loadFont();
        },
        changeWeight()
        {
            if (fontWeight === 'bold')
            {
                fontWeight = 'regular';
            }
            else
            {
                fontWeight = 'bold';
            }

            loadFont();
        },
        changeBevel()
        {
            bevelEnabled = !bevelEnabled;

            refreshText();
        }
    };

    //

    const gui = new GUI();

    gui.add(params, 'changeColor').name('change color');
    gui.add(params, 'changeFont').name('change font');
    gui.add(params, 'changeWeight').name('change weight');
    gui.add(params, 'changeBevel').name('change bevel');
    gui.open();

    //

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    windowHalfX = window.innerWidth / 2;

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

//

function onDocumentKeyDown(event: KeyboardEvent)
{
    if (firstLetter)
    {
        firstLetter = false;
        text = '';
    }

    const keyCode = event.keyCode;

    // backspace

    if (keyCode === 8)
    {
        event.preventDefault();

        text = text.substring(0, text.length - 1);
        refreshText();

        return false;
    }
}

function onDocumentKeyPress(event: KeyboardEvent)
{
    const keyCode = event.which;

    // backspace

    if (keyCode === 8)
    {
        event.preventDefault();
    }
    else
    {
        const ch = String.fromCharCode(keyCode);
        text += ch;

        refreshText();
    }
}

function loadFont()
{
    const loader = new FontLoader3D();
    loader.load(`${rootPath}fonts/${fontName}_${fontWeight}.typeface.json`, function (response)
    {
        font = response;

        refreshText();
    });
}

function createText()
{
    textGeo = $set(new TextGeometry3D(), {
        text, parameters: {

            font,

            size,
            height,
            curveSegments,

            bevelThickness,
            bevelSize,
            bevelEnabled

        }
    });

    textGeo.computeBoundingBox();

    const centerOffset = -0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);

    textMesh1 = new Node3D().addComponent('Mesh3D', { geometry: textGeo, materials });

    textMesh1.entity.position.x = centerOffset;
    textMesh1.entity.position.y = hover;
    textMesh1.entity.position.z = 0;

    textMesh1.entity.rotation.x = 0;
    textMesh1.entity.rotation.y = Math.PI * 2;

    group.addChild(textMesh1.entity);

    if (mirror)
    {
        textMesh2 = new Node3D().addComponent('Mesh3D', {
            geometry: textGeo, materials
        });

        textMesh2.entity.position.x = centerOffset;
        textMesh2.entity.position.y = -hover;
        textMesh2.entity.position.z = height;

        textMesh2.entity.rotation.x = Math.PI;
        textMesh2.entity.rotation.y = Math.PI * 2;

        group.addChild(textMesh2.entity);
    }
}

function refreshText()
{
    group.removeChild(textMesh1?.entity);
    if (mirror) group.removeChild(textMesh2?.entity);

    if (!text) return;

    createText();
}

function onPointerDown(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    pointerXOnPointerDown = event.clientX - windowHalfX;
    targetRotationOnPointerDown = targetRotation;

    document.addEventListener('pointermove', onPointerMove);
    document.addEventListener('pointerup', onPointerUp);
}

function onPointerMove(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    pointerX = event.clientX - windowHalfX;

    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
}

function onPointerUp(event: PointerEvent)
{
    if (event.isPrimary === false) return;

    document.removeEventListener('pointermove', onPointerMove);
    document.removeEventListener('pointerup', onPointerUp);
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
}

function render()
{
    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;

    camera.entity.lookAt(cameraTarget);

    renderer.clear();
    renderer.render(scene, camera);
}
